package com.example.yupao.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.yupao.constant.RedisConstant;
import com.example.yupao.constant.UserConstant;
import com.example.yupao.enums.StatusEnum;
import com.example.yupao.exception.BusinessException;
import com.example.yupao.mapper.TagMapper;
import com.example.yupao.model.domain.Tag;
import com.example.yupao.model.request.SearchUsersByTagsRequest;
import com.example.yupao.model.request.UserSearchRequest;
import com.example.yupao.model.request.UserUpdateRequest;
import com.example.yupao.model.vo.UserVO;
import com.example.yupao.service.UserService;
import com.example.yupao.model.domain.User;
import com.example.yupao.mapper.UserMapper;
import com.example.yupao.utils.AlgorithmUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.example.yupao.constant.UserConstant.USER_LOGIN_STATUS;

/**
 * @author 86138
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2024-08-26 14:35:32
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private TagMapper tagMapper;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    //加密盐
    private static final String SALT = "hhf";

    @Override
    public List<User> searchUsersByTags(SearchUsersByTagsRequest searchUsersByTagsRequest) {


        if (CollUtil.isEmpty(searchUsersByTagsRequest.getTagIdList())) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "参数不能为空");
        }

//        //方法一 通过like查询
//        long start = System.currentTimeMillis();
//        //1.建立查询条件
//        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//        tagNameList.stream().forEach(tagName -> {
//            wrapper.like(User::getTags,tagName);
//        });
//        //2.查询
//        List<User> userList = this.list(wrapper);
//        //3.脱敏
//        List<User> safetyUserList = userList.stream().map(user -> {
//            return getSafetyUser(user);
//        }).collect(Collectors.toList());
//        //4.log
//        long end = System.currentTimeMillis();
//        log.info("searchUsersByTags query time=========>" + (end - start));
//        //5.返回
//        return safetyUserList;


        //方法二 通过内存查询
        long start = System.currentTimeMillis();
        //1.查询所有用户
        Page<User> page = this.page(new Page<>(searchUsersByTagsRequest.getCurrent(), searchUsersByTagsRequest.getPageSize()));
        List<User> userList = page.getRecords();
        //2.根据查询条件过滤
        List<User> collect = userList.stream().filter(user -> {
            if (StrUtil.isBlank(user.getTags())) {
                return false;
            }
            List<Integer> tempTagIdList = JSONUtil.toList(user.getTags(), Integer.class);
            //Java8特性 判断 List是否为null 为空则创建空对象
            tempTagIdList = Optional.ofNullable(tempTagIdList).orElse(List.of());
            return tempTagIdList.containsAll(searchUsersByTagsRequest.getTagIdList());
        }).collect(Collectors.toList());
        //3.tag映射(id -> name)
        List<User> mapCollection = mapTagId2TagName(collect);
        //4.脱敏
        List<User> safetyUserList = mapCollection.stream().map(this::getSafetyUser).collect(Collectors.toList());
        //5.log
        long end = System.currentTimeMillis();
        log.info("searchUsersByTags query time=========>" + (end - start));
        //6.返回
        return safetyUserList;
    }

    @Override
    public long register(String account, String password, String checkPassword) {

        //1.账号密码校验
        if (StringUtils.isAnyBlank(account, password, checkPassword)) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "参数错误");
        }

        if (account.length() < 4 || password.length() < 8) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "账号密码不符合规范");
        }

        //3.账号不包含特殊字符
        String checkValid = "[!@#$%^&*()]";
        Matcher matcher = Pattern.compile(checkValid).matcher(account);
        if (matcher.find()) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "账号包含特殊字符");
        }

        //4.密码与重复密码一致
        if (!password.equals(checkPassword)) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "密码与校验密码不一致");
        }

        //2.账号是否重复(少进行一次数据库操作)
        boolean exists = this.lambdaQuery().eq(User::getAccount, account).exists();
        if (exists) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "账号已存在");
        }

        //5.密码加密
        String encryptPassword = encryptedPassword(password);

        //6.插入数据库
        User user = new User();
        user.setAccount(account);
        user.setPassword(encryptPassword);
        boolean save = this.save(user);

        if (!save) {
            throw new BusinessException(StatusEnum.SYSTEM_ERROR);
        }
        return user.getId();
    }

    @Override
    public User login(String account, String password, HttpServletRequest request) {

        //1.校验用户名和密码
        if (StringUtils.isAnyBlank(account, password) || account.length() < 4 || password.length() < 8) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "账号密码不符合规范");
        }
        //2.密码比对
        User originUser = this.lambdaQuery().eq(User::getAccount, account).one();
        if (originUser == null) {
            log.info("user login failed,user cannot exist");
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "账号不存在");
        }
        if (!(encryptedPassword(password)).equals(originUser.getPassword())) {
            log.info("user login failed,user password cannot match");
            throw new BusinessException(StatusEnum.PARAMS_ERROR, "密码错误");
        }
        //3.保存登录状态
        HttpSession session = request.getSession();
        session.setAttribute(USER_LOGIN_STATUS, originUser);

        //4.用户信息脱敏
        User safetyUser = getSafetyUser(originUser);


        //5.返回用户信息
        return safetyUser;
    }

    /**
     * 数据脱敏
     *
     * @param originUser
     * @return
     */
    private User getSafetyUser(User originUser) {

        if (originUser == null) {
            return null;
        }

        User handlerUser = new User();
        handlerUser.setId(originUser.getId());
        handlerUser.setUsername(originUser.getUsername());
        handlerUser.setAccount(originUser.getAccount());
        handlerUser.setProfile(originUser.getProfile());
        handlerUser.setGender(originUser.getGender());
        handlerUser.setAvatarUrl(originUser.getAvatarUrl());
        handlerUser.setPhone(originUser.getPhone());
        handlerUser.setEmail(originUser.getEmail());
        handlerUser.setRole(originUser.getRole());
        handlerUser.setStatus(originUser.getStatus());
        handlerUser.setCreateTime(originUser.getCreateTime());
        handlerUser.setTags(originUser.getTags());
        return handlerUser;
    }

    /**
     * 映射 id -> name
     *
     * @param userList
     * @return
     */
    private List<User> mapTagId2TagName(List<User> userList) {
        return userList.stream().map(user -> {
            //3.1 反序列化
            List<Integer> tempTagIdList = JSONUtil.toList(user.getTags(), Integer.class);
            if (tempTagIdList.isEmpty() || tempTagIdList.size() < 1){
                return user;
            }
            //3.2 根据id 搜索 tag
            List<Tag> tagList = tagMapper.selectBatchIds(tempTagIdList);
            //3.3 映射 [1,2] -> ["Java","Vue"]
            List<String> tagNameList = tagList
                    .stream()
                    .map(Tag::getTagName)
                    .collect(Collectors.toList());
            //3.4 序列化
            String tags = JSONUtil.toJsonStr(tagNameList);
            //3.5 重新赋值
            user.setTags(tags);
            return user;
        }).collect(Collectors.toList());
    }


    @Override
    public List<User> search(UserSearchRequest userSearchRequest) {


        //查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

        wrapper.like(StrUtil.isNotBlank(userSearchRequest.getUsername()), User::getUsername, userSearchRequest.getUsername());
        wrapper.like(StrUtil.isNotBlank(userSearchRequest.getPhone()), User::getPhone, userSearchRequest.getPhone());
        wrapper.like(StrUtil.isNotBlank(userSearchRequest.getAccount()), User::getAccount, userSearchRequest.getAccount());
        wrapper.like(StrUtil.isNotBlank(userSearchRequest.getEmail()), User::getEmail, userSearchRequest.getEmail());
        wrapper.eq(userSearchRequest.getGender() != null, User::getGender, userSearchRequest.getGender());
        Date createTime = userSearchRequest.getCreateTime();
        if (createTime != null) {
            DateTime start = DateUtil.beginOfDay(createTime);
            DateTime end = DateUtil.endOfDay(createTime);
            wrapper.between(User::getCreateTime, start, end);
        }
        wrapper.eq(userSearchRequest.getStatus() != null, User::getStatus, userSearchRequest.getStatus());


        //获取数据
        List<User> userList = this.list(wrapper);

        //数据脱敏
        List<User> safeUser = userList.stream().map(user -> getSafetyUser(user)).collect(Collectors.toList());

        return safeUser;
    }

    @Override
    public User getCurrentUser(HttpServletRequest request) {

        //1.从session中取出凭证
        HttpSession session = request.getSession();
        Object userObj = session.getAttribute(USER_LOGIN_STATUS);
        User user = (User) userObj;
        if (user == null) {
            throw new BusinessException(StatusEnum.NO_LOGIN);
        }
        //2.查询数据库,获取用户信息
        User currentUser = this.getById(user.getId());

        //3.脱敏返回数据
        User safetyUser = this.getSafetyUser(currentUser);
        return safetyUser;

    }

    @Override
    public void logout(HttpServletRequest request) {

        //1.重新设置session的值
        HttpSession session = request.getSession();

        session.removeAttribute(USER_LOGIN_STATUS);

    }

    @Override
    public boolean updateByCondition(UserUpdateRequest userUpdateRequest, HttpServletRequest request) {

        if (userUpdateRequest.getId() == null) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR);
        }
        //如果用户只传递了id,其他为空,则抛出异常
        //1.排除属性id的影响
        UserUpdateRequest tempUserUpdateRequest = new UserUpdateRequest();
        BeanUtil.copyProperties(userUpdateRequest, tempUserUpdateRequest, "id");
        //2.进行判断
        if (areAllFieldsNull(tempUserUpdateRequest)) {
            throw new BusinessException(StatusEnum.PARAMS_ERROR);
        }


        //1.校验用户是否有修改权限(只能自己修改或者管理员修改)
        User currentUser = getCurrentUser(request);

        if ((UserConstant.DEFAULT_ROLE == currentUser.getRole() && userUpdateRequest.getId() == currentUser.getId()) || UserConstant.ADMIN_ROLE == currentUser.getRole()) {

            //2.进行修改
            User user = BeanUtil.toBean(userUpdateRequest, User.class);

            //3.返回信息
            return this.updateById(user);
        }
        //无权修改
        throw new BusinessException(StatusEnum.NO_AUTH, "无权限修改");


    }

    @Override
    public Page<User> recommendUsers(UserSearchRequest userSearchRequest, HttpServletRequest request) {

        Page<User> page = null;
        int current = userSearchRequest.getCurrent();
        int pageSize = userSearchRequest.getPageSize();

        //1.获取当前用户
        User user = getCurrentUser(request);

        //2.redis查看是否存在缓存
        Object userPageObj = redisTemplate.opsForValue().get(RedisConstant.RECOMMEND_USER + user.getId());

        //3.若存在,则从缓存中取出
        if (userPageObj != null){
            Page<User> cachePage = BeanUtil.toBean(userPageObj, Page.class);
            return cachePage;
        }
        //4.否则,查询数据库
        // TODO 登录,推荐算法
        if (user != null) {
            page = this.page(Page.of(current,pageSize));
        } else {//未登录 查询前五条
            page = this.page(Page.of(current,pageSize));
        }

        if (page.getRecords().size() == 0 || page.getRecords().isEmpty()){
            throw new BusinessException(StatusEnum.NULL_ERROR);
        }

        //映射标签 map id -> username
        List<User> userList = mapTagId2TagName(page.getRecords());
        //脱敏
        List<User> safetyUserList = userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
        //重新赋值
        page.setRecords(safetyUserList);

        //5.保存到redis
        redisTemplate.opsForValue().set(RedisConstant.RECOMMEND_USER + user.getId(),page,25, TimeUnit.HOURS);

        //6.返回数据
        return page;
    }

    @Override
    public List<UserVO> matchTeam(Long num,HttpServletRequest request) {
        //1.请求参数校验,若为空则默认匹配5人
        if (num == null || num <= 0){
            num = 5L;
        }
        //2.查询所有user

        //优化 只查询需要的字段 以及 排除tags == null或者tags == [] 的数据
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.isNotNull(User::getTags);
        wrapper.ne(User::getTags,"[]");

        List<User> dbUserList = this.list(wrapper);
        //3.反序列化tags标签
        User currentUser = this.getCurrentUser(request);
        String tagsJson = currentUser.getTags();
        List<String> tagList = JSONUtil.toList(tagsJson, String.class);
        //4.遍历 根据匹配算法进行算分 SortedMap<user,score>
//        SortedMap<User,Integer> indexDistanceMap = new TreeMap<>();
        List<Pair<User,Integer>> indexDistanceList = new ArrayList<>();
        dbUserList.forEach(dbUser -> {
            String userTagsJson = dbUser.getTags();
            //数据为空排除
            if (StringUtils.isBlank(userTagsJson) || userTagsJson == "[]"){
                return;
            }
            //自己,排除
            if (dbUser.getId() == currentUser.getId()){
                return;
            }
            List<String> userTagList = JSONUtil.toList(userTagsJson, String.class);
            //算法得分
            int score = AlgorithmUtil.minDistance(tagList,userTagList);
            indexDistanceList.add(Pair.of(dbUser,score));
        });
        //5.按编辑距离由小到大排序
        List<Pair<User, Integer>> sortedPairList = indexDistanceList
                .stream()
                .sorted(Comparator.comparingInt(Pair::getValue))
                .limit(num)
                .toList();
        //5.取前num个score低的userId => user

        List<User> userList = sortedPairList.stream().map(pair -> {
            User user = pair.getKey();
            return user;
        }).toList();
        List<UserVO> result = mapTagId2TagName(userList)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserVO.class))
                .toList();

        //6.返回数据
        return result;
    }


    /**
     * 密码加密
     *
     * @param password
     * @return
     */
    private String encryptedPassword(String password) {

        //现在暂时先用spring自带的加密算法
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + password).getBytes());

        return encryptPassword;
    }

    /**
     * 通过反射判断一个对象的属性是否全为空
     *
     * @param obj
     * @return
     * @throws IllegalAccessError
     */
    private static boolean areAllFieldsNull(Object obj) {
        if (obj == null) {
            return true;
        }

        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);  // 使私有字段也可以访问
            Object fieldValue = null;

            try {
                fieldValue = field.get(obj);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

            if (fieldValue != null) {
                return false;
            }
        }
        return true;
    }
}




